package custom;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class GPProxy {

    public static void main(String[] args) {
        custom.Singer target = new custom.Singer();
        custom.ISinger proxy = (custom.ISinger) GPProxy.newProxyInstance(
                new custom.GPClassLoader(),
                target.getClass().getInterfaces(),
                new custom.GPInvocationHandle() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("自定义向观众问好");//标黄
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                        System.out.println("自定义谢谢大家");//标黄
                        return returnValue;
                    }
                });
        proxy.sing();
    }

    public static Object newProxyInstance(custom.GPClassLoader classLoader, Class<?>[] interfaces, custom.GPInvocationHandle h) {
        try {
            //1、动态生成源代码.java文件
            String src = generateSrc(interfaces);
            //2、文件输出磁盘
            String path = GPProxy.class.getResource("").getPath();
            File file = new File(path + "$Proxy0.java");
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(src);
            fileWriter.flush();
            fileWriter.close();

            //3、把生成的.java文件按编译成.class文件

            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
            ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager standardFileManager = javaCompiler.getStandardFileManager(null, null, StandardCharsets.UTF_8);
            Iterable<? extends JavaFileObject> javaFileObjects = standardFileManager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardFileManager, null, null, null, javaFileObjects);
            task.call();
            standardFileManager.close();

            //4、编译生成的.class文件加载到JVM中

            Class<?> proxyClass = classLoader.findClass("$Proxy0");
            Constructor<?> constructor = proxyClass.getConstructor(custom.GPInvocationHandle.class);
            Object o = constructor.newInstance(h);
            file.delete();
            return o;
            //5、返回字节码重组以后的新的代理对象
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }


    private static String generateSrc(Class<?>[] interfaces) {
        String ln = "\r\n";
        StringBuilder sb = new StringBuilder();
        sb.append(interfaces[0].getPackage() + ";" + ln);
        sb.append("import java.lang.reflect.Method;" + ln);
        List<String> collect = Arrays.stream(interfaces).map(Class::getName).collect(Collectors.toList());
        sb.append("public class $Proxy0 implements " + String.join(",", collect) + "{" + ln);
        sb.append("     GPInvocationHandle h;" + ln);
        sb.append("         public $Proxy0(GPInvocationHandle h){" + ln);
        sb.append("         this.h=h;}" + ln);

        Method[] methods = interfaces[0].getMethods();
        for (Method method : methods) {
            sb.append("@Override\r\n" + ln);
            sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "(){\r\n" + ln);
            sb.append("try{\n" + ln);
            sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{});\n" + ln);
            sb.append("this.h.invoke(this, m, (Object[])null);\n" + ln);
            sb.append("}catch(Throwable e){\n" + ln);
            sb.append("e.printStackTrace();}\n" + ln);
            sb.append("}" + ln);
        }
        sb.append("}");


        return sb.toString();

    }

}
